﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Djs.Tools.Tresor.Data
{
    public class Bits
    {
        #region Masky
        /// <summary>
        /// Vrátí hodnotu daného bitu (bit: 0 - 31) z dané hodnoty (value): vrací hodnoty 1 nebo 0.
        /// Pokud daný bit je "1", vrací 1, jinak vrací 0.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt32 Bit(UInt32 value, byte bit)
        {
            CheckBit32(bit);
            return (UInt32)(((value & Instance.Bit32[bit]) == 0) ? 0 : 1);
        }
        /// <summary>
        /// Vrátí hodnotu daného bitu (bit: 0 - 63) z dané hodnoty (value): vrací hodnoty 1 nebo 0.
        /// Pokud daný bit je "1", vrací 1, jinak vrací 0.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt64 Bit(UInt64 value, byte bit)
        {
            CheckBit64(bit);
            return (UInt64)(((value & Instance.Bit64[bit]) == 0) ? 0 : 1);
        }
        /// <summary>
        /// Vrátí danou hodnotu, v níž nastaví daný bit na danou hodnotu; ostatní bity ponechá.
        /// Pokud hodnota "set" bude == 0, pak daný bit vynuluje, pokud "set" bude jiná než 0, pak daný bit nastaví na 1.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bit"></param>
        /// <param name="set"></param>
        /// <returns></returns>
        public static UInt32 Set(UInt32 value, byte bit, UInt32 set)
        {
            CheckBit32(bit);
            UInt32 mask = Instance.Bit32[bit];                    // Maska daného jednoho bitu (například pro bit = 4 je mask = 0x0010)
            UInt32 state = value & mask;                          // Jaký je stav bitu nyní
            if (state == 0 && set != 0)                           // Nyní je 0 a má být 1:
                value = value | mask;
            else if (state != 0 && set == 0)                      // Nyní je 1 a má být 0:
                value = value & GetMask320(bit);
            return value;
        }
        /// <summary>
        /// Vrátí danou hodnotu, v níž nastaví daný bit na danou hodnotu; ostatní bity ponechá.
        /// Pokud hodnota "set" bude == 0, pak daný bit vynuluje, pokud "set" bude jiná než 0, pak daný bit nastaví na 1.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt64 Set(UInt64 value, byte bit, UInt64 set)
        {
            CheckBit32(bit);
            UInt64 mask = Instance.Bit64[bit];                    // Maska daného jednoho bitu (například pro bit = 4 je mask = 0x0010)
            UInt64 state = value & mask;                          // Jaký je stav bitu nyní
            if (state == 0L && set != 0L)                         // Nyní je 0 a má být 1:
                value = value | mask;
            else if (state != 0L && set == 0L)                    // Nyní je 1 a má být 0:
                value = value & GetMask640(bit);
            return value;
        }
        /// <summary>
        /// Vrací dolních (bits) bitů z dané hodnoty. Například z hodnoty value = 0x0895 (b 0000 1000 1001 0101) a bits = 6 vrací 0x0015 (b 0000 0000 0001 0101).
        /// Pro hodnotu bits = 0 vrací 0. Hodnota bits může být v rozsahu 0 ÷ 32 včetně.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bits"></param>
        /// <returns></returns>
        public static UInt32 Mask(UInt32 value, byte bits)
        {
            CheckBits32(bits);
            return (UInt32)(value & Instance.Mask32[bits]);
        }
        /// <summary>
        /// Vrací dolních (bits) bitů z dané hodnoty. Například z hodnoty value = 0x0895 (b 0000 1000 1001 0101) a bits = 6 vrací 0x0015 (b 0000 0000 0001 0101).
        /// Pro hodnotu bits = 0 vrací 0. Hodnota bits může být v rozsahu 0 ÷ 64 včetně.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="bits"></param>
        /// <returns></returns>
        public static UInt64 Mask(UInt64 value, byte bits)
        {
            CheckBits64(bits);
            return (UInt64)(value & Instance.Mask64[bits]);
        }
        /// <summary>
        /// Vrátí masku pro daný bit. Maska má nastaven daný bit na 1, ostatní bity na 0.
        /// </summary>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt32 GetMask321(byte bit)
        {
            CheckBit32(bit);
            return Instance.Bit32[bit];
        }
        /// <summary>
        /// Vrátí masku pro daný bit. Maska má nastaven daný bit na 1, ostatní bity na 0.
        /// </summary>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt64 GetMask641(byte bit)
        {
            CheckBit64(bit);
            return Instance.Bit64[bit];
        }
        /// <summary>
        /// Vrátí inverzní masku pro daný bit. Maska má nastaven daný bit na 0, ostatní bity na 1.
        /// </summary>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt32 GetMask320(byte bit)
        {
            CheckBit32(bit);
            return Instance.Bit32[bit] ^ Instance.Mask32[32];
        }
        /// <summary>
        /// Vrátí inverzní masku pro daný bit. Maska má nastaven daný bit na 0, ostatní bity na 1.
        /// </summary>
        /// <param name="bit"></param>
        /// <returns></returns>
        public static UInt64 GetMask640(byte bit)
        {
            CheckBit64(bit);
            return Instance.Bit64[bit] ^ Instance.Mask64[64];
        }
        /// <summary>
        /// Pokud bit je v rozmezí 0 až 31 včetně, je to OK. Jinak hodí chybu.
        /// </summary>
        /// <param name="bit"></param>
        public static void CheckBit32(int bit)
        {
            if (bit < 0) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bit\" is out of range for bit operation.");
            if (bit > 31) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bit\" is out of range for 32bit operation.");
        }
        /// <summary>
        /// Pokud bit je v rozmezí 0 až 32 včetně, je to OK. Jinak hodí chybu.
        /// </summary>
        /// <param name="bit"></param>
        public static void CheckBits32(int bit)
        {
            if (bit < 0) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bits\" is out of range for bit operation.");
            if (bit > 32) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bits\" is out of range for 32bit operation.");
        }
        /// <summary>
        /// Pokud bit je v rozmezí 0 až 63 včetně, je to OK. Jinak hodí chybu.
        /// </summary>
        /// <param name="bit"></param>
        public static void CheckBit64(int bit)
        {
            if (bit < 0) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bit\" is out of range for bit operation.");
            if (bit > 63) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bit\" is out of range for 64bit operation.");
        }
        /// <summary>
        /// Pokud bit je v rozmezí 0 až 64 včetně, je to OK. Jinak hodí chybu.
        /// </summary>
        /// <param name="bit"></param>
        public static void CheckBits64(int bit)
        {
            if (bit < 0) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bits\" is out of range for bit operation.");
            if (bit > 64) throw new ArgumentException("Value " + bit.ToString() + " of argument \"bits\" is out of range for 64bit operation.");
        }
        #endregion
        #region Čtení a zápisy do bufferu
        /// <summary>
        /// Přečte hodnotu UInt32 z daného bufferu. Čte 4 byte, pořadí = Lo-Hi.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="start"></param>
        /// <returns></returns>
        public static UInt32 ReadUInt32(byte[] buffer, int start)
        {
            UInt32 b0 = buffer[start];
            UInt32 b1 = (UInt32)(buffer[start + 1]) << 8;
            UInt32 b2 = (UInt32)(buffer[start + 2]) << 16;
            UInt32 b3 = (UInt32)(buffer[start + 3]) << 24;
            return b0 | b1 | b2 | b3;
        }
        /// <summary>
        /// Zapíše hodnotu UInt32 do daného bufferu. Píše 4 byte, pořadí = Lo-Hi.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static void WriteUInt32(byte[] buffer, int start, UInt32 value)
        {
            buffer[start] = (byte)(value & 0xFF);
            buffer[start + 1] = (byte)((value >> 8) & 0xFF);
            buffer[start + 2] = (byte)((value >> 16) & 0xFF);
            buffer[start + 3] = (byte)((value >> 24) & 0xFF);
        }
        #endregion
        #region protected singleton, private instanční proměnné
        /// <summary>
        /// Singleton instance, obsahující pole masek a bitů
        /// </summary>
        protected static Bits Instance
        {
            get
            {
                if (__Instance == null)
                {
                    lock (__Locker)
                    {
                        if (__Instance == null)
                            __Instance = new Bits();
                    }
                }
                return __Instance;
            }
        }
        private static Bits __Instance = null;
        private static object __Locker = new object();
        /// <summary>
        /// Konstruktor = iniciace polí
        /// </summary>
        private Bits()
        {
            this.Bit32 = new UInt32[32];
            this.Mask32 = new UInt32[33];
            this.Bit64 = new UInt64[64];
            this.Mask64 = new UInt64[65];
            UInt32 b32 = 1;
            UInt32 m32 = 0;
            UInt64 b64 = 1L;
            UInt64 m64 = 0L;
            for (int v = 0; v < 65; v++)
            {
                if (v < 32)
                {
                    this.Bit32[v] = b32;
                    b32 = b32 << 1;
                }
                if (v < 33)
                {
                    this.Mask32[v] = m32;
                    m32 = (m32 << 1) | 1;
                }
                if (v < 64)
                {
                    this.Bit64[v] = b64;
                    b64 = b64 << 1;
                }
                if (v < 65)
                {
                    this.Mask64[v] = m64;
                    m64 = (m64 << 1) | 1;
                }
            }
        }
        /// <summary>
        /// Pole, obsahující nastavený vždy jeden bit, odpovídající indexu pole.
        /// Tak například na indexu [3] je hodnota 0x0008, na indexu [0] je hodnota 0x0001 atd.
        /// Pole má 32 prvků, pro 32 bitů (pozice 0 ÷ 31).
        /// </summary>
        private UInt32[] Bit32;
        /// <summary>
        /// Pole, obsahující masku, která vrací daný počet bitů počínaje od bitu 0.
        /// Tak například na indexu [3] je hodnota 0x0007 (vrací 3 dolní bity), na indexu [0] je hodnota 0 (žádné bity).
        /// Pole má 33 prvků, pro hodnotu 0 a poté pro 32 bitů.
        /// </summary>
        private UInt32[] Mask32;
        /// <summary>
        /// Pole, obsahující nastavený vždy jeden bit, odpovídající indexu pole.
        /// Tak například na indexu [3] je hodnota 0x0008, na indexu [0] je hodnota 0x0001 atd.
        /// Pole má 64 prvků, pro 64 bitů (pozice 0 ÷ 63).
        /// </summary>
        private UInt64[] Bit64;
        /// <summary>
        /// Pole, obsahující masku, která vrací daný počet bitů počínaje od bitu 0.
        /// Tak například na indexu [3] je hodnota 0x0007 (vrací 3 dolní bity), na indexu [0] je hodnota 0 (žádné bity).
        /// Pole má 65 prvků, pro hodnotu 0 a poté pro 64 bitů.
        /// </summary>
        private UInt64[] Mask64;

        #endregion
    }
}
